/*
 * File Name: proxy.c
 *
 * Group Members: Cassie Chin (cassiechin9793@gmail.com)
 *                Stephen Smetana (stephensmetana11@gmail.com)
 *                Logan Widick (logan.widick@gmail.com)
 *
 * Sources: http://beej.us/guide/bgnet/examples/server.c
 *          This code is originally intended to send the string "Hello, World!\n"
 *          out over a stream connection.
 *
 *          We took the outline of this code and incorporated the ability to
 *          perform actions in a child process. This child process recieves
 *          HTTP requests from a client.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <stddef.h>
#include <sys/stat.h>
#include <assert.h>
#include "httpstuff.h"
#include "filter.h"
#include "blacklist.h"
#include <fcntl.h>  


/* We can delete everything we don't need here before we turn in the project 
 Turn statement to ON to use any of these features */
#define DEBUG_OFF
#define SETSOCKOPT_OFF
#define REAP_DEAD_PROCESSES_OFF

#define PORT "60003"  // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold
#define BUFFERSIZE 256

#define PROXY_NAME "http://localhost:60003/"

void main_child(int fd);

char * custom_fgets(int fd, char*buffer, int buffer_size){

	// do stuff


}


void sigchld_handler(int s) {
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*) sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*) sa)->sin6_addr);
}

int main(void) {
	int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	char s[INET6_ADDRSTRLEN];
	int rv;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP

	if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		freeaddrinfo(servinfo);
		return 1;
	}

	// loop through all the results and bind to the first we can
	for (p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
				== -1) {
			perror("server: socket");
			continue;
		}

#ifdef SETSOCKOPT_ON
		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
						sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}
#endif

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("server: bind");
			continue;
		}

		break;
	}

	if (p == NULL) {
		fprintf(stderr, "server: failed to bind\n");
		return 2;
	}

	freeaddrinfo(servinfo); // all done with this structure

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		exit(1);
	}

#ifdef REAP_DEAD_PROCESSES_ON
	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		perror("sigaction");
		exit(1);
	}
#endif

	printf("server: waiting for connections...\n");

	while (1) { // main accept() loop
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
		if (new_fd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family,
				get_in_addr((struct sockaddr *) &their_addr), s, sizeof s);
		printf("server: got connection from %s\n", s);

		/* BEGIN CHILD PROCESS : HTTP request processing is done here*/
		if (!fork()) {
			close(sockfd); // child doesn't need the listener
			main_child(new_fd);
			close(new_fd);
			exit(0);
		}
		/* END CHILD PROCESS */
		close(new_fd); // parent doesn't need this
	}

	return 0;
}

void send501(FILE *sock_out_fp) {
	HTTPMsg *proxy_resp = new_HTTPMsg();
	proxy_resp->version = 1.0;
	proxy_resp->statusCode = 501;

	//make the body
	proxy_resp->fileptr = tmpfile();
	char tmp_body_buffer[BUFFER_SIZE+1];
	snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>501 Not Implemented</h1></html>\n");
	fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), proxy_resp->fileptr);
	fflush(proxy_resp->fileptr);
	fseek(proxy_resp->fileptr, 0, SEEK_END);
	proxy_resp->contentLength = (int)ftell(proxy_resp->fileptr);

	// send over sock_out_fd
	HTTPMsg_sendMsg(proxy_resp, sock_out_fp);
	delete_HTTPMsg(proxy_resp);
}

void send503(FILE* sock_out_fp) {
	HTTPMsg *proxy_resp = new_HTTPMsg();
	proxy_resp->version = 1.0;
	proxy_resp->statusCode = 503;
	
//make the body
	proxy_resp->fileptr = tmpfile();
	char tmp_body_buffer[BUFFER_SIZE+1];
	snprintf(tmp_body_buffer, BUFFER_SIZE, "<html><h1>503 Error</h1></html>\n");
	fwrite(tmp_body_buffer, sizeof(char), strlen(tmp_body_buffer), proxy_resp->fileptr);
	fflush(proxy_resp->fileptr);
	fseek(proxy_resp->fileptr, 0, SEEK_END);
	proxy_resp->contentLength = (int)ftell(proxy_resp->fileptr);

	// send over sock_out_fd
	HTTPMsg_sendMsg(proxy_resp, sock_out_fp);
	delete_HTTPMsg(proxy_resp);
}

FILE* open_file(char *path, bool isWrite)
{
    if(!path) return NULL;
    else if(!isWrite) return fopen(path, "r");
    else{
	//find first slash.
	int i = strlen(path)-1;
	for( ; i >= 0; i--)
	{
	    if(path[i] == '/') break;
	}
	
	char *substring = calloc(i+1, sizeof(char));
	strncpy(substring, path, i);
	
	//make the intermediate directories. 
	char *syscall_cmd = calloc(strlen("mkdir -p ") + strlen(substring) + 3, sizeof(char));
	sprintf(syscall_cmd, "mkdir -p %s", substring);
	printf("System call to make directory returned %d\n", system(syscall_cmd));
	free(syscall_cmd);
	free(substring);
	
	return fopen(path, "w");
    }
}

void main_child(int new_fd) {
	//    struct sockaddr_in client_address;
	HTTPMsg *recv_msg = NULL;
	HTTPMsg *proxy_resp = NULL;
	HTTPMsg *server_resp = NULL;

	FILE *proxy_to_server_fp = NULL;
	FILE *server_to_proxy_fp = NULL;
	char *filePath = NULL;

	char xfer_buffer[BUFFER_SIZE + 1];
	int num_xfer = 0;

	//set file to nonblocking. 
	int flags = fcntl(new_fd, F_GETFL, 0); 
	flags |= O_NONBLOCK; 
	fcntl(new_fd, F_SETFL, flags);
	
	//try to open file descriptors
	FILE* client_to_proxy_fp = fdopen(new_fd, "r");
	FILE* proxy_to_client_fp = fdopen(dup(new_fd), "w");
	
	//// I dont know how to change this line	
	if (!client_to_proxy_fp || !proxy_to_client_fp)
		exit(0);
	
	//// This needs more changes than just changning to the int new_fd, does this need changing?
	setvbuf(client_to_proxy_fp, NULL, _IONBF, 0);
 	setvbuf(proxy_to_client_fp, NULL, _IONBF, 0);
	int parse_code = -3;
	
	// Open, Create and Close the Blacklist file
	FILE * blacklist_input = fopen ("blacklist.txt", "r");
    	if (blacklist_input == NULL) printf(" Error! blacklist.txt not found! \n");
	Blacklisted *blacklist = new_Blacklisted(blacklist_input);
	fclose(blacklist_input);

	// Open, Create and Close the Badwords file        
	FILE *badWordsFile = fopen ("badwords.txt", "r");
	Filter *filter = new_Filter (badWordsFile);
	if (filter == NULL) printf("Error! badwords.txt not found! \n");
	fclose(badWordsFile);


printBlacklist(blacklist);
	//The steps repeat for additional messages
	while (1) {

		// #2 -- complete
		fprintf(stderr, "Parsing client request.\n");
		fflush(stderr);
		recv_msg = new_HTTPMsg();
		recv_msg->isRequest = true;
		//// changed this so new_fd is now passed into it. 
		parse_code = HTTPMsg_doParsing(recv_msg, client_to_proxy_fp, new_fd, false); 
		if(parse_code != 0){
			fprintf(stderr, "doParsing on client request returned %d\n", parse_code);
			delete_HTTPMsg(recv_msg);
			//// Change to new_fd?
			send501(proxy_to_client_fp);
			recv_msg = NULL;
			 break;
		}

		//remove the accept-encoding header. we do not allow gzip, deflate, etc. 
		HeaderList_removeHeaderByKey(recv_msg->headerList, "Accept-Encoding");
		HeaderList_removeHeaderByKey(recv_msg->headerList, "Connection");
		HeaderList_addHeader(recv_msg->headerList, "Connection", "close");
		HeaderList_removeHeaderByKey(recv_msg->headerList, "Bytes");
		HeaderList_removeHeaderByKey(recv_msg->headerList, "Range");
		recv_msg->version = 1.0f;
		HTTPMsg_print(recv_msg, stderr);
		fprintf(stderr, "Parsed client request.\n");
		fflush(stderr);

		//we should have reached either the BODY state or the END state.
		if (recv_msg->state != BODY && recv_msg->state != END) {
			fprintf(stderr, "Invalid ending state. Exiting.\n");
			//// new_fd?
			send501(proxy_to_client_fp);
			delete_HTTPMsg(recv_msg);
			recv_msg = NULL;
			break;
		}

		//Do the url blacklist detection here. BREAK if the url is blacklisted.
     if (isThisBlacklisted(blacklist , recv_msg->host)){
      printf("YOU SHALL NOT LOAD\n%s is Blacklisted!\n", recv_msg->host);
	  //// new_fd?
      send501(proxy_to_client_fp);
	delete_HTTPMsg(recv_msg);
			recv_msg = NULL;
      break;
      }	
		if(1)
		{
			filePath = HTTPMsg_findFilePath(recv_msg, false);

			//no file path; send "not implemented" message
			if (filePath == NULL && !HTTPMsg_isDynamic(recv_msg, false)) {
				fprintf(stderr, "Cannot make a file path.\n");
				//// ??
				send501(proxy_to_client_fp);
				delete_HTTPMsg(recv_msg);
			recv_msg = NULL;
				break;
			}
			//file path exists. let's try to find the file.
			else {
				FILE *file_to_send = open_file(filePath, false);				

				//send from cache
				if (file_to_send && !HTTPMsg_isDynamic(recv_msg, false)) {
					fprintf(stderr, "Sending data from cache for resource %s\n", recv_msg->resource);
					//make message; set headers, version, status
					proxy_resp = new_HTTPMsg();
					proxy_resp->isRequest = false;
					proxy_resp->version = 1.0;
					proxy_resp->statusCode = 200;
					if(!proxy_resp->path) proxy_resp->path = strdup(recv_msg->path);
					if(!proxy_resp->host) proxy_resp->host = strdup(recv_msg->host);

					//HTTPMsg_prependStringToResource(proxy_resp, PROXY_NAME);

					fseek(file_to_send, 0, SEEK_END);

					//filtering does not impact content length, as each char in a bad word gets replaced with a *
					// (at least, this is true under ASCII). 
					proxy_resp->contentLength = (int) ftell(file_to_send); 
				
					//head: headers only. No filtering required, and filtering does not impact content length.
					if (strcasecmp(recv_msg->method, "HEAD") == 0) {
						HTTPMsg_sendMsg(proxy_resp, proxy_to_client_fp);
						fclose(file_to_send);
					}
					
					//just send it already!
					else{
						proxy_resp->fileptr = file_to_send;
						HTTPMsg_sendMsg(proxy_resp, proxy_to_client_fp);	
					}
					HTTPMsg_print(proxy_resp, stderr);
					fprintf(stderr, "Sent data from cache for resource %s\n", recv_msg->resource);
					delete_HTTPMsg(proxy_resp);
					proxy_resp = NULL;
				}

				//do not send from cache. request from server.
				else {
					//have we opened a connection?
					if (proxy_to_server_fp == NULL
							|| server_to_proxy_fp == NULL) {
						FilePointerSet mySet;
						fprintf(stderr, "Trying to open conn to host %s and port %s\n", recv_msg->host, recv_msg->port);
						if (openClientConnection(recv_msg->port, recv_msg->host, &mySet) != 0) {
							fprintf(stderr, "UNABLE TO OPEN CONNECTION!\n");
							send503(proxy_to_client_fp);
							delete_HTTPMsg(recv_msg);
							recv_msg = NULL;
							break;
						} else {
							proxy_to_server_fp = mySet.outFile;
							server_to_proxy_fp = mySet.inFile;
						}
					}
					
					//we should have a connection. Send the request.
					fprintf(stderr, "About to send request for resource %s\n", recv_msg->resource);
					
					if (HTTPMsg_sendMsg(recv_msg, proxy_to_server_fp) != 0) {
						//connection impossible; return 503
						fprintf(stderr, "UNABLE TO SEND RECV_MSG TO SERVER\n");
						send503(proxy_to_client_fp);
						delete_HTTPMsg(recv_msg);
						recv_msg = NULL;
						break;
					}
					fprintf(stderr, "Sent the request for resource %s\n", recv_msg->resource);
		
					//now let's get the response.
					fprintf(stderr, "Parsing the server's reply for resource %s\n", recv_msg->resource);
						
					server_resp = new_HTTPMsg();
					server_resp->isRequest = false;
					parse_code = HTTPMsg_doParsing(server_resp, server_to_proxy_fp, false);
					if(!server_resp->resource) server_resp->resource = strdup(recv_msg->resource);
					if(parse_code != 0)
					{
						fprintf(stderr, "HTTPMsg_doParsing for server returned %d\n", parse_code);
						delete_HTTPMsg(recv_msg);
						recv_msg = NULL;
						break;
					}
					HTTPMsg_print(server_resp, stderr);
					fprintf(stderr, "Done parsing the server's reply for resource %s\n", recv_msg->resource);
					
					//check validity of response
					if (server_resp->state != BODY
							&& server_resp->state != END) {
						fprintf(stderr, "SERVER response has invalid state.\n");
						send503(proxy_to_client_fp);
						delete_HTTPMsg(recv_msg);
						recv_msg = NULL;
						break;
					}

					fprintf(stderr, "Begin filterchk resource %s\n", recv_msg->resource);
					//filter here.
					if(HTTPMsg_shouldFilter(server_resp)){
						fprintf(stderr, "Beginning filtering resource %s\n", recv_msg->resource);
						FILE *filtered = tmpfile();
						Filter_filterFile (filter, server_resp->fileptr, filtered );
						fclose (server_resp->fileptr);
						server_resp->fileptr = filtered;
						fprintf(stderr, "End filtering resource %s\n", recv_msg->resource);
					}
					fprintf(stderr, "end filterchk resource %s\n", recv_msg->resource);

					//if we have a 2xx and a file pointer, save if not dynamic
					if (server_resp->statusCode >= 200
							&& server_resp->statusCode < 300
							&& server_resp->fileptr
							&& !HTTPMsg_isDynamic(recv_msg, false)
							&& !HTTPMsg_isDynamic(server_resp, false)) {
						fprintf(stderr, "Begin saving resource %s\n", recv_msg->resource);
						char *newFilePath = HTTPMsg_findFilePath(server_resp, false);
						if(newFilePath)
						{
							free(filePath);
							filePath = NULL;	
						}			
						else{
							newFilePath = filePath;
							filePath = NULL;	//prevents double free.
						}			

						//try to save the file
						FILE* local_save_file = open_file(newFilePath, true);
						if (local_save_file) {
							fprintf(stderr, "Saving a local copy\n");
							rewind(server_resp->fileptr);
							while (1) {
								num_xfer = fread(xfer_buffer, sizeof(char),
										BUFFER_SIZE, server_resp->fileptr);
								
								if (num_xfer <= 0)
									break;
								fwrite(xfer_buffer, sizeof(char), num_xfer,
										local_save_file);
								fflush(local_save_file);
							}
							fclose(local_save_file);
							fprintf(stderr, "Done saving a local copy\n");
						}

						//now free new file path
						if (newFilePath) free(newFilePath);
						newFilePath = NULL;
						fprintf(stderr, "End saving resource %s\n", recv_msg->resource);
					}

					fprintf(stderr, "Sending server's reply to client for resource %s\n", recv_msg->resource);

					//we are actually sending content; modify content-length header if needed. 
					HTTPMsg_sendMsg(server_resp, proxy_to_client_fp, new_fd);
					fprintf(stderr, "Done sending server's reply to client  for resource %s\n", recv_msg->resource);
					
					if(server_to_proxy_fp) fclose(server_to_proxy_fp);
					if(proxy_to_server_fp) fclose(proxy_to_server_fp);
					proxy_to_server_fp = NULL;
					server_to_proxy_fp = NULL;					

					delete_HTTPMsg(server_resp);
					server_resp = NULL;
					delete_HTTPMsg(recv_msg);
					recv_msg = NULL;
				}
			}
		}
		if (filePath)
			free(filePath);
		filePath = NULL;
		if (proxy_resp)
			delete_HTTPMsg(proxy_resp);
		proxy_resp = NULL;
		if (recv_msg)
			delete_HTTPMsg(recv_msg);
		recv_msg = NULL;
		if (server_resp)
			delete_HTTPMsg(server_resp);
		server_resp = NULL;
	}
	//cleanup
	if (filePath)
		free(filePath);
	if (proxy_to_server_fp)
		fclose(proxy_to_server_fp);
	if (server_to_proxy_fp)
		fclose(server_to_proxy_fp);
	if (proxy_to_client_fp)
		fclose(proxy_to_client_fp);
	if (client_to_proxy_fp)
		fclose(client_to_proxy_fp);
	if (proxy_resp)
		delete_HTTPMsg(proxy_resp);
	proxy_resp = NULL;
	if (recv_msg)
		delete_HTTPMsg(recv_msg);
	recv_msg = NULL;
	if (server_resp)
		delete_HTTPMsg(server_resp);
	server_resp = NULL;

	//delete the blacklist
  	delete_Blacklisted(blacklist);
  
	//delete the bad words filter
	delete_Filter(filter);
}